import base64
import json
import logging
import os
import re
import time
from io import BytesIO
from logging import FileHandler
import requests
import subprocess
from tkinter.messagebox import askokcancel, showerror, showinfo
from threading import Thread
from PIL import Image
from easydict import EasyDict
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from .settings import PATH, HEADERS


"""这一部分是关于Selenium的辅助函数"""


def click_by_js(browser: webdriver.Chrome, element):
    """
    使用js点击某一个元素
    Args:
        browser: 浏览器对象
        element: 要点击的元素
    Returns:
        None
    """
    browser.execute_script("arguments[0].click();", element)


class BaseSelenium:
    """需要操作网页的类需要继承此类，提供了基础的操作"""

    def __init__(self):
        self.timeout = 20
        self.browser = None
        self.env_ok = False

    def enter_text(self, selector, text, timeout=None):
        if isinstance(selector, tuple):
            entry = self.find_element(selector, timeout)
        else:
            entry = selector
        entry.clear()
        entry.send_keys(text)
        return entry

    def click_button(self, selector, interval=-1, timeout=None):
        button = self.find_button(selector, timeout)
        if interval > 0:
            time.sleep(interval)
        click_by_js(self.browser, button)
        return button

    def get_text(self, selector, strip=True):
        element = self.find_element(selector)
        text = element.get_attribute('textContent')
        if strip:
            text = text.strip()
        return text

    def find_element(self, selector, timeout=None):
        timeout = timeout or self.timeout
        element = WebDriverWait(self.browser, timeout). \
            until(EC.visibility_of_element_located(selector))
        return element

    def find_elements(self, selector, timeout=None):
        timeout = timeout or self.timeout
        element = WebDriverWait(self.browser, timeout). \
            until(EC.visibility_of_all_elements_located(selector))
        return element

    def find_button(self, selector, timeout=None):
        timeout = timeout or self.timeout
        button = WebDriverWait(self.browser, timeout). \
            until(EC.element_to_be_clickable(selector))
        return button

    def get_pid(self):
        if not self.browser:
            return None
        return self.browser.service.process.pid

    def get_memory(self):
        if not self.browser:
            return 0
        child = get_child_process(self.get_pid())
        memory = 0
        for _, pid in child:
            memory += get_proc_memory(pid)
        return memory


def get_browser(headless=True, maximize=True, load_image=False) -> webdriver.Chrome:
    option = webdriver.ChromeOptions()
    if headless:                            # 设置为无头浏览器模式
        option.add_argument('--headless')
        option.add_argument('--disable-gpu')

    # 让网页以中文的形式显示
    option.add_argument("--lang=zh-cn")
    # 隐藏Chrome正在被自动化软件控制”的通知
    option.add_argument("--disable-infobars")

    # 解决反爬识别selenium
    option.add_experimental_option('excludeSwitches', ['enable-automation'])
    option.add_experimental_option('useAutomationExtension', False)
    # option.page_load_strategy = "none"
    # 禁止图片加载，提高速度
    if not load_image:
        prefs = {"profile.managed_default_content_settings.images": 2}
        option.add_experimental_option("prefs", prefs)
    # 改变页面加载策略，不等待页面全部加载完成再返回
    # DesiredCapabilities.CHROME["pageLoadStrategy"] = "none"
    desired_capabilities = DesiredCapabilities.CHROME
    desired_capabilities["pageLoadStrategy"] = "eager"

    browser = webdriver.Chrome(executable_path=str(PATH.driver/"chromedriver"),
                               options=option, service_args=['hide_console', ],
                               desired_capabilities=desired_capabilities)
    browser.set_page_load_timeout(60)
    browser.set_script_timeout(60)
    # 解决反爬识别selenium
    browser.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
        'source': 'Object.defineProperty(navigator, "webdriver", {get: () => undefined})'
    })

    if maximize:
        browser.maximize_window()
        # 手动设置分辨率是因为在使用无头浏览器测试时，出现了最大化后的浏览器分辨率只有800*600的问题，
        # 导致了网页元素加载异常，所以这里进行了手动的设置，1920*1080可以适配大部分屏幕的使用
        # print(browser.get_window_size()) --> {'width': 800, 'height': 600}
        browser.set_window_size(1920, 1080)

    return browser

"""Selenium的辅助函数结束"""

def get_logger(log_dir, log_name):
    # 创建logger对象。传入logger名字
    logger = logging.getLogger(log_name)
    log_path = log_dir / log_name
    log_dir.mkdir(parents=True, exist_ok=True)
    # 设置日志记录等级
    logger.setLevel(logging.INFO)
    file_handler = FileHandler(filename=log_path, encoding="utf-8")
    # interval 滚动周期，
    # when="MIDNIGHT", interval=1 表示每天0点为更新点，每天生成一个文件
    # backupCount  表示日志保存个数
    # file_handler = TimedRotatingFileHandler(
    #     filename=log_path, when="MIDNIGHT", interval=1, backupCount=30
    # )
    # # filename="mylog" suffix设置，会生成文件名为mylog.2020-02-25.log
    # file_handler.suffix = "%Y-%m-%d.log"
    # # extMatch是编译好正则表达式，用于匹配日志文件名后缀
    # # 需要注意的是suffix和extMatch一定要匹配的上，如果不匹配，过期日志不会被删除。
    # file_handler.extMatch = re.compile(r"^\d{4}-\d{2}-\d{2}.log$")
    # 定义日志输出格式
    file_handler.setFormatter(
        logging.Formatter(
            "[%(asctime)s] [%(process)d] [%(levelname)s] - %(module)s.%(funcName)s (%(filename)s:%(lineno)d) - %(message)s"
        )
    )
    logger.addHandler(file_handler)
    return logger


def run_async(func):
    """让函数异步执行的装饰器"""
    def wrapper(*args, **kwargs):
        thr = Thread(target=func, args=args, kwargs=kwargs)
        thr.setDaemon(True)
        thr.start()

    return wrapper


def run_anyway(func):
    """忽略一切错误"""
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except:
            return None

    return wrapper


def read_json(file, EMPTY=None):
    txt = file.read_text(encoding="utf-8")
    data = json.loads(txt)

    if EMPTY:
        new_data = EMPTY.copy()
        new_data.update(data)
        if len(new_data) != len(data):
            write_json(file, new_data)
        data = new_data

    return EasyDict(data)


def write_json(file, data):
    jd = json.dumps(data, indent=4)
    file.write_text(jd, encoding="utf-8")


def clear_log():
    for log_dir in PATH.log.iterdir():
        if not log_dir.is_dir():
            continue
        log_list = list(log_dir.iterdir())
        log_list.sort()
        for log in log_list[:-30]:
            try:
                os.remove(log)
            except:
                pass


def open_file_by_notepad(file):
    if file.is_file():
        subprocess.run(f'notepad {file}', shell=False)


def delete_file(file, name):
    if askokcancel("确认清空", f"是否确定清空{name}?该操作不可撤销。"):
        if not file.is_file():
            showinfo(title="成功", message="文件清空成功！")
        else:
            try:
                os.remove(file)
                showinfo(title="成功", message="文件清空成功！")
            except Exception as e:
                showerror(title='错误', message=f'文件清空错误: {e}')


def get_web_file(url, timeout=10):
    try:
        res = requests.get(url, headers=HEADERS, timeout=timeout)
        return res.text
    except:
        return None


def get_gitee_json(url: str, timeout=10):
    try:
        res = requests.get(url, headers=HEADERS, timeout=timeout)
        if res.text == '':
            url = url.replace("config/", "config/backup/").split(".json")[0]
            return get_gitee_json(url)
        return res.text
    except:
        return None


def timeout_cmd(cmd, timeout=2):
    """为了防止os.popen阻塞无法返回，这里使用poll，如果超时就返回"""
    startupinfo = subprocess.STARTUPINFO()
    startupinfo.dwFlags = subprocess.CREATE_NEW_CONSOLE | subprocess.STARTF_USESHOWWINDOW
    startupinfo.wShowWindow = subprocess.SW_HIDE
    sub2 = subprocess.Popen(cmd, shell=False, startupinfo=startupinfo)
    start = time.time()
    while time.time() - start <= timeout:
        res = sub2.poll()
        if isinstance(res, str):
            return res
        elif res == 0:
            break
            # sub2.kill()
        else:
            time.sleep(0.1)
    return ""


def get_proc_memory(process, threshold=-1):
    """返回进程名所占的内存信息"""
    if not process:
        return 0
    mode = isinstance(process, str)
    if mode:
        cmd = f'tasklist /fi "imagename eq {process}" | findstr.exe {process}'
    else:
        cmd = f'tasklist /fi "pid eq {process}" | findstr.exe {process}'
    result = timeout_cmd(cmd).strip()
    if result.strip() == "":
        return {} if mode else 0
    result = [r.split() for r in result.split("\n")]
    for i in range(len(result)):
        result[i][4] = int(result[i][4].replace(",", "")) // 1024
    result = {int(r[1]): r[4] for r in result if int(r[4]) > threshold}

    return result if mode else result[process]


def kill_process(pid):
    if isinstance(pid, str):
        cmd = f"taskkill /f /t /im {pid}"
    else:
        cmd = f"taskkill /f /t /pid {pid}"
    result = timeout_cmd(cmd)

    return True if result or result.startswith("成功") else False


def get_child_process(pid):
    """获得进程的所有子进程，包括子进程的子进程"""
    cmd = f"wmic process where ParentProcessId={pid} get Caption,ProcessId"
    result = timeout_cmd(cmd)
    result = [r.strip().split() for r in result.strip().split("\n") if r]
    if len(result) > 1:
        child = [(r[0], int(r[1])) for r in result[1:]]
        for _, pid in result[1:]:
            child += get_child_process(pid)
    else:
        return []

    return child


def get_parent_process(pid):
    """获得子进程的父进程，返回一个（进程名，pid）的元组或者空元组"""
    cmd = f"wmic process where processid={pid} get Caption,parentprocessid"
    result = timeout_cmd(cmd)
    result = [r.strip().split() for r in result.strip().split("\n") if r]
    if len(result) > 1:
        parent = result[1]
        return parent[0], int(parent[1])
    else:
        return ()


def print_log(log, debug=False):
    if not debug:
        print(log)


def pil2bytes(im, img_type='png', b64=False):
    """将PIL.Image格式的图像转化为二进制数据"""
    bf = BytesIO()
    im.save(bf, img_type)
    img = bf.getvalue()

    return img


def bs64_to_image(base64_str):
    base64_data = re.sub('^data:image/.+;base64,', '', base64_str)
    byte_data = base64.b64decode(base64_data)
    # image_data = BytesIO(byte_data)
    # img = Image.open(image_data)

    return byte_data


def merge_image(im1, im2):
    if not im1 and not im2:
        return None
    elif not im1:
        return im2
    elif not im2:
        return im1

    w1, h1 = im1.size
    w2, h2 = im2.size
    assert w1 == w2
    im = Image.new(im1.mode, (w1, h1+h2), (255,255,255))
    im.paste(im1, (0, 0))
    im.paste(im2, (0, h1))

    return im


def send_email(to, msg, file):
    pass


class UserInfo(EasyDict):
    credit_type = "心"
    credit_level = 0
    week_avg = 0
    day_avg = []
    svip = False
    fox = 0
    downright = 0
    near_week_check = 0
    last_week_check = 0

    def __init__(self, name, mirror=False):
        super().__init__()
        self.mirror = mirror
        self.name = name

    def __repr__(self):
        if self.mirror:
            return f"用户名: {self.name}, " \
                   f"周均: {self.week_avg}, 降权: {self.downright}, " \
                   f"近一周查询: {self.near_week_check}, 上一周查询: {self.last_week_check}"
        else:
            return f"用户名: {self.name}, 信誉: {self.credit_level}{self.credit_type}, " \
                   f"超级会员: {'是' if self.svip else '否'}, " \
                   f"周均: {self.week_avg}, 日均: {self.day_avg}, 降权: {self.downright}, " \
                   f"近一周查询: {self.near_week_check}, 上一周查询: {self.last_week_check}"
